Release 10.1A: OpenEdge Development:
Progress 4GL Handbook


Deleting persistent procedures

Whenever your code is done using a persistent procedure, you must remember to delete it to clean up after yourself. Use this statement:

DELETE PROCEDURE procedure-handle [ NO-ERROR ]. 

The NO-ERROR option suppresses any error message, for example, if the procedure has already been deleted elsewhere. You can also use the VALID-HANDLE function, which you have seen in AppBuilder-generated code, to check whether a handle is still valid or not, such as in this example:

IF VALID-HANDLE(hProc) THEN 
    DELETE PROCEDURE hProc. 

When you close one of the Order windows by clicking the close icon in the right-hand corner, the window disappears. Is its procedure really gone? Yes, but this doesn’t happen automatically. It’s very important for you to make sure that you remember to clean up persistent procedures when your application is done with them, if the AppBuilder doesn’t do it for you.

In this case, the AppBuilder generated just the code you need to make sure the procedure is deleted when you close the window. You might remember this code from Chapter 4, "Introducing the OpenEdge AppBuilder," but it should mean a lot more to you now because you understand about the THIS-PROCEDURE handle, the PERSISTENT attribute, and procedure handles. The code is in the disable_UI internal procedure. To review, here’s the sequence again:

  1. The main block, which executes as soon as the procedure starts up, sets up a trigger to RUN the internal procedure disable_UI on the CLOSE event:
  2. /* The CLOSE event can be used from inside or outside the procedure to  */ 
    /* terminate it.                                                        */ 
    ON CLOSE OF THIS-PROCEDURE  
       RUN disable_UI. 
    

  3. The AppBuilder generates code for the WINDOW-CLOSE event of the window, which is what fires when you click the Close icon in the window:
  4. DO: 
      /* This event will close the window and terminate the procedure.  */ 
      APPLY "CLOSE":U TO THIS-PROCEDURE. 
      RETURN NO-APPLY. 
    END. 
    

  5. The APPLY “CLOSE” statement sets off the CLOSE event for the procedure handle itself. This runs disable_UI, which not only deletes the window with the DELETE WIDGET statement but also has the statement to delete the procedure, which might have looked cryptic when you first saw it, but which you should fully understand now:
  6. /* Delete the WINDOW we created */ 
       IF SESSION:DISPLAY-TYPE = "GUI":U AND VALID-HANDLE(C-Win) 
       THEN DELETE WIDGET C-Win. 
       IF THIS-PROCEDURE:PERSISTENT THEN DELETE PROCEDURE THIS-PROCEDURE. 
    END PROCEDURE. 
    

  7. The PERSISTENT attribute on the procedure handle identifies it as a persistent procedure, and the DELETE PROCEDURE statement deletes it by identifying its handle, THIS-PROCEDURE.

If the AppBuilder didn’t do this for you, the procedure and all its memory and other resources would sit around until your session ended. This might create quite a mess, not just because of the memory it uses, but because of the possibility of records it might be holding and any other resources that could cause errors or contention in your application. The worst part is that the window itself would be gone, so you would have no visual clue that the procedure is still there.

To test your application so you can see what procedures are in memory:

  1. Run h-CustOrderWin6.w.
  2. Select several Orders to display in their window.
  3. Select the Procedures icon from the PRO*Tools palette:
  4. The Procedure Object Viewer appears:

    This tool shows that you have the main window and three copies of h-OrderWin.w running. Remember that whenever you run a procedure from the AppBuilder, it saves it to a temporary file and runs the temporary file, so you see a temporary filename such as p89200cf.ab in your temporary file directory instead of its actual procedure filename h-CustOrderWin6.w.

    The Procedure Object Viewer window has a host of useful information in it, including all the INTERNAL-ENTRIES for any procedure you select from the list. You can also run any internal entry by clicking the Run entry button.

    If you want to get rid of a running procedure from here, you can click the Delete button. If it’s an AppBuilder-generated procedure such as those you’ve been looking at, you can also click the Apply “Close” button, which should execute all the proper cleanup code for the procedure that’s associated with the CLOSE event.

  5. Close the Customers and Orders window and select View Refresh List of Procedure Objects in the Procedure Object Viewer.

You should see that all the running procedures, including both the main window and any Order windows you have open, are gone. So, are you done cleaning up?

Not yet! The AppBuilder is doing you another favor here, and it is one that is rather dangerous, because if you forget to test your application outside the AppBuilder, you might not see that you still have work to do. When you click Stop, the AppBuilder deletes any persistent procedures that were started since you first chose the Run button, to make it easier for you to test parts of applications over and over. But when you run your application from outside the OpenEdge tools, you need to take care of your own cleanup. If you were to run h-CustOrderWin6.w from another procedure, open several Order windows, and then close h-CustOrderWin6, the Order windows (and their procedures, of course) would still be running.

To add code to delete those procedures when the main window that starts them up is deleted:

  1. Go into the Definitions section for h-CustOrderWin6.w and add these definitions to the end of the section:
  2. DEFINE VARIABLE cOrderHandles AS CHARACTER  NO-UNDO. 
    DEFINE VARIABLE iHandle       AS INTEGER    NO-UNDO. 
    DEFINE VARIABLE hOrderWindow  AS HANDLE     NO-UNDO. 
    

    Your new code uses these variables to save off a list of all the procedure handles of the procedures you create, and then later walks through them and deletes them.

  3. Add another line to the CHOOSE trigger for the BtnDetail button to save off the new procedure handle in a list:
  4. DO: 
        DEFINE VARIABLE hOrder AS HANDLE      NO-UNDO. 
        RUN h-OrderWin.w PERSISTENT SET hOrder (BUFFER Order). 
        cOrderHandles = cOrderHandles +  
            (IF cOrderHandles NE "" THEN "," ELSE "") + STRING(hOrder). 
    END. 
    

    To save a list of handles, you need to turn each handle into a STRING. The commas act as a delimiter between entries in the list. The IF-THEN-ELSE clause inside parentheses adds a comma only if there’s already something in the list.

  5. Go into the main block and add some code just before it runs disable_UI to clean up those procedure handles:
  6. ON CLOSE OF THIS-PROCEDURE  
    DO: 
        /* Cleanup any OrderLine windows that might still be open. */ 
        DO iHandle = 1 TO NUM-ENTRIES(cOrderHandles): 
            hOrderWindow = WIDGET-HANDLE(ENTRY(iHandle, cOrderHandles)). 
            IF VALID-HANDLE (hOrderWindow) THEN  
                APPLY "CLOSE" TO hOrderWindow. 
        END. 
        RUN disable_UI. 
    END. 
    

This DO block turns each handle value back into the right data type using the WIDGET-HANDLE function. (The function you use is the same one for all handles, even though this is a procedure handle, not a widget handle, for a visual object like a button.)

You could execute the DELETE PROCEDURE hOrderWindow NO-ERROR statement. The NO-ERROR option would let you use the DELETE PROCEDURE statement without bothering to check whether the handle is still valid. Remember that the user might or might not have closed it on his own.

But because the h-OrderWin.w procedure has special code to process the CLOSE event and do additional types of cleanup, it’s always better to APPLY “CLOSE” to an AppBuilder-generated procedure window or any other procedure that uses the same convention. The VALID-HANDLE check makes sure that the window has not already been closed and the procedure deleted.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095